
// database internal structures
var database_listeners = [];
var database_subscriptions = [];
var database_elements = {};
var database_mostrecent = {};
var database_resyncer = null;
var asink = false;

// add a listener to database activity
// calls logic.database_listen() on local storage event
// the goal is to decouple components that share data on client
function DatabaseListen(logic) {
  database_listeners.push(logic);
}

// debug util
function DatabaseDebug(obj) {
  var str = "what";
  for(i in obj) {
    if(obj[i]) {
      str += i.toString() + " = " + obj[i].toString() + "\n";
    }
  }
  alert(str);
}

// concat two things as a file paths
function DatabaseConcat(a,b) {
    if(a == null && b==null) return "";
    if(a == null) return b;
    if(b == null) return a;
    if(b[0] == '/') b = b.substring(1);
    if(a[a.length-1] == '/') return a+b;
    return a+'/'+b;
}

var count = 1;

// update a subscription to a database channel
function DatabaseCallback(logic,path,data) {

    // no content?
    if(!data && logic.database_update_empty ) {
      logic.database_update_empty();
      return false;
    }

    // decode
    if(!data) return false;
    var data2 = data;

    data = eval(data); // actually will just crash here in the eval...
    if(!data) { alert("error evaluating " + data2 ); return false; }

    // batch?
    if( logic.database_update_all ) {
      logic.database_update_all(data);
      return false;
    }

    // xxx this whole caching solution needs to handle multiple tables

    // if one item store and call callback
    if(data.constructor != Array) {

      // if no id then do not cache
      if(data && !data.id && logic.database_update) {
        logic.database_update(data);
      }

      // cache
      if(data.id && !database_elements[data.id]) {
        if(!database_mostrecent[path] || datum.id>database_mostrecent[path] ) {
          database_mostrecent[path] = data.id;
          // xxx bad hack; so that entering new input is also tracked... xxx
          database_mostrecent[path+"/#"] = data.id;
        }
        database_elements[data.id] = data;
        for(var i in data) {
          if (data[i]) data[i] = unescape(data[i]);
        }
        if(logic.database_update) {
          logic.database_update(data);
        }
      }
      return false;
    }

    // no content?
    if(!data.length && logic.database_update_empty ) {
      logic.database_update_empty();
    }
    // store all and call callback for each; in reverse order to preserve time
    for(var i = data.length - 1; i >= 0; i-- ) {
      var datum = data[i].elem;

     // if no id then do not cache
      if(datum && !datum.id && logic.database_update) {
        logic.database_update(datum);
      }
      // else try to cache
      if(datum && datum.id && !database_elements[datum.id] ) {
        if(!database_mostrecent[path] || datum.id>database_mostrecent[path] ) {
          database_mostrecent[path] = datum.id;
          // xxx bad hack; so that entering new input is also tracked... xxx
          database_mostrecent[path+"/#"] = datum.id;
        }
        database_elements[datum.id] = datum;
        for(var j in datum) {
          if( datum[j] ) datum[j] = unescape(datum[j]);
        }
        if(logic.database_update) {
          logic.database_update(datum);
        }
      }
    }

    return false;
}

// this exists to instance separate vars in js heap to avoid a race condition
function DatabaseResyncElem(focus) {
  var youngest = database_mostrecent[focus.uid];
  var params = "path="+escape(focus.uid)+"&order=desc&offset=0&limit=30&newer=" 
                  + youngest + "&" + focus.query;

  // ask the server for data at subscription; and publish to that observer
  new Ajax.Request( focus.jsonurl + '/json_query' ,
   { asynchronous: asink, // xxx cannot use async because of global shared vars
     method: 'post',
     parameters: params,
     evalScripts:true,
     onComplete:function(r) { 
       DatabaseCallback(focus.logic,focus.uid,r.responseText);
     }
   });
}

// request resync with server on all things we are listening to
function DatabaseResync() {
  // xxx we should conflate all these requests into a single stream plz xxx
  // xxx race condition with global vars that forces me to turn off async.
  for(var i = 0; i < database_subscriptions.length; i++ ) {
     DatabaseResyncElem(database_subscriptions[i]);
  }
}
 

// remove from db
function DatabaseDelete(uid) {

    var jsonurl = "/note"

    // where to save posts to in the server side database such as /username/chat
    if(uid == null || uid[0] != '/') {
      if (uid == null) uid = "#";
      // this gets past the domain name to the path portion
      // for example given http://y.x.com/a/b/c you get /y/a/b/c
      // we use this to help automate where chat should store posts
      // a well written server can store posts under a uid matching the url.
      var h = location.host.split('.');
      // xxx improve to handle x.y.site.com
      if(h.length<3) h = ""; else h = h[0]+"/";
      var l = location.href.substring(location.href.indexOf('/',7));
      if(l.length <2 ) l = "";
      uid = DatabaseConcat(h,DatabaseConcat(l,uid));
    }

    var params = "path="+escape(uid);

    new Ajax.Request( jsonurl + '/json_delete' ,
     { method: 'post',
       asynchronous: asink, // xxx safer
       evalScripts: true,
       parameters: params,
       onComplete:function(r) { }
     });
}

// add to db
// can i consolidate DatabaseStore(), DatabaseGet() and DatabaseSubscribe() xxx
function DatabaseStore(logic,uid,obj,jsonurl) {

    // valid?
    if(!logic || (!logic.database_update && !logic.database_update_all)) {
      return;
    }

    // where to save posts to in the server side database such as /username/chat
    if(uid == null || uid[0] != '/') {
      if (uid == null) uid = "#";
      // this gets past the domain name to the path portion
      // for example given http://y.x.com/a/b/c you get /y/a/b/c
      // we use this to help automate where chat should store posts
      // a well written server can store posts under a uid matching the url.
      var h = location.host.split('.');
      // xxx improve to handle x.y.site.com
      if(h.length<3) h = ""; else h = h[0]+"/";
      var l = location.href.substring(location.href.indexOf('/',7));
      if(l.length <2 ) l = "";
      uid = DatabaseConcat(h,DatabaseConcat(l,uid));
    }

    // guess where you might have made your json gateway public on your server
    // var jsonurl = null;
    if(jsonurl == null) {
      jsonurl = "/note"
    }

    // notify registered local listeners
    for(var i = 0 ; i < database_listeners.length; i++) {
      database_listener = database_listeners[i];
      database_listener.database_listen(obj);
    }

    // submit this to make the post xxx send json
    var params = "path="+escape(uid);
    for(var i in obj) {
      if(obj[i]) {
        params += "&" + i.toString() + "=" + escape(obj[i].toString());
      }
    }

    // only get most recent if in a channel we're subscribing to
    if(database_mostrecent[uid]) {
      params = params + "&newer=" + database_mostrecent[uid];
    }

    // submit the post event - it returns the equivalent of a resync
    // (calling for a resync instead - failed
    // - it might have been db lock failing which i subsequently fixed xxx)
    new Ajax.Request( jsonurl + '/json_post' ,
     { method: 'post',
       asynchronous: asink, // xxx safer
       evalScripts: true,
       parameters: params,
       onComplete:function(r) { DatabaseCallback(logic,uid,r.responseText); }
     });

    return false;
}

// ask for something specifically; this emulates a typical internal query
// can i consolidate DatabaseStore(), DatabaseGet() and DatabaseSubscribe() xxx
function DatabaseGet(logic,uid,query,offset,limit) {

    // valid?
    if(!logic || (!logic.database_update && !logic.database_update_all)) {
      return;
    }

    // where to save posts to in the server side database such as /username/chat
    if(uid == null || uid[0] != '/') {
      if (uid == null) uid = "";
      // this gets past the domain name to the path portion
      // for example given http://y.x.com/a/b/c you get /y/a/b/c
      // we use this to help automate where chat should store posts
      // a well written server can store posts under a uid matching the url.
      var h = location.host.split('.');
      // xxx improve to handle x.y.site.com
      if(h.length<3) h = ""; else h = h[0]+"/";
      var l = location.href.substring(location.href.indexOf('/',7));
      if(l.length <2 ) l = "";
      uid = DatabaseConcat(h,DatabaseConcat(l,uid));
    }

    // guess where you might have made your json gateway public on your server
    var jsonurl = null;
    if(jsonurl == null) {
      jsonurl = "/note"
    }

    // misc
    if (!offset) offset = 0;
    if (!limit) limit = 99;

    // build up a request to get something from the server
    var focus = new Object();
    focus.uid = uid;
    focus.jsonurl = jsonurl;
    focus.logic = logic;
    focus.offset = offset;
    focus.limit = limit;

    // ask right now
    var params = "path="+escape(focus.uid) + "&offset="+offset+"&limit="+limit 
              + "&" + query ;

    // ask right now
    new Ajax.Request( focus.jsonurl + '/json_query' ,
     { asynchronous: asink, // xxx safer
       method: 'post',
       parameters: params,
       evalScripts:true,
       onComplete:function(r) { 
          DatabaseCallback(focus.logic,focus.uid,r.responseText);
       }
     });
}

// add subscription to database - calls logic.database_callback on updates
// can i consolidate DatabaseStore(), DatabaseGet() and DatabaseSubscribe() xxx
function DatabaseSubscribe(logic,uid,query,offset,limit) {

    // valid?
    if(!logic || (!logic.database_update && !logic.database_update_all)) {
      return;
    }

    // where to save posts to in the server side database such as /username/chat
    if(uid == null || uid[0] != '/') {
      if (uid == null) uid = "";
      // this gets past the domain name to the path portion
      // for example given http://y.x.com/a/b/c you get /y/a/b/c
      // we use this to help automate where chat should store posts
      // a well written server can store posts under a uid matching the url.
      // so say a chat area would store posts at /site/myarea/chat/1..2..3...
      var h = location.host.split('.');
      // xxx improve to handle x.y.site.com
      if(h.length<3) h = ""; else h = h[0]+"/";
      var l = location.href.substring(location.href.indexOf('/',7));
      if(l.length <2 ) l = "";
      uid = DatabaseConcat(h,DatabaseConcat(l,uid));
    }

    // guess where you might have made your json gateway public on your server
    var jsonurl = null;
    if(jsonurl == null) {
      jsonurl = "/note"
    }

    // misc
    if (!offset) offset = 0;
    if (!limit) limit = 99;

    // build up a request to get something from the server
    var focus = new Object();
    focus.uid = uid;
    focus.jsonurl = jsonurl;
    focus.logic = logic;
    focus.offset = offset;
    focus.limit = limit;
    focus.query = query;

    // just store the request; will be asked later
    database_subscriptions.push(focus);

    // start a syncronization handler ( will not start for 4 more seconds )
    // note that should all be consolidated into a single request later xxx
    if(!database_resyncer) {
      database_resyncer = new PeriodicalExecuter( DatabaseResync, 4 );
    }

    // Ask for immediate resync; to update subscription asap ( at zero seconds )
    DatabaseResync();
}

// xxx it would be more elegant to batch up user windowing operations
// instead of interactive server activity

